package com.kexilo.core.common.aspect;

import com.kexilo.core.common.annotation.Retry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Random;

/**
 * 重试切面处理器
 *
 * @author Kexilo
 */
@Aspect
@Component
public class RetryAspect {
    
    private static final Logger log = LoggerFactory.getLogger(RetryAspect.class);
    private static final Random random = new Random();

    @Around("@annotation(retry)")
    public Object doRetry(ProceedingJoinPoint joinPoint, Retry retry) throws Throwable {
        String methodName = getMethodName(joinPoint);
        
        Exception lastException = null;
        long delay = retry.delay();
        
        for (int attempt = 1; attempt <= retry.maxAttempts(); attempt++) {
            try {
                log.debug("执行方法: {}, 第{}次尝试", methodName, attempt);
                return joinPoint.proceed();
                
            } catch (Exception e) {
                lastException = e;
                
                // 检查是否需要重试
                if (!shouldRetry(e, retry)) {
                    log.warn("方法 {} 执行失败，异常类型不重试: {}", methodName, e.getClass().getSimpleName());
                    throw e;
                }
                
                // 最后一次尝试，不再重试
                if (attempt == retry.maxAttempts()) {
                    log.error("方法 {} 执行失败，已达到最大重试次数: {}", methodName, retry.maxAttempts());
                    break;
                }
                
                // 计算下次重试延迟
                long nextDelay = calculateDelay(retry, attempt, delay);
                
                log.warn("方法 {} 第{}次执行失败，{}ms后进行第{}次重试。异常: {}", 
                        methodName, attempt, nextDelay, attempt + 1, e.getMessage());
                
                try {
                    Thread.sleep(nextDelay);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("重试被中断", ie);
                }
                
                delay = nextDelay;
            }
        }
        
        throw lastException;
    }

    /**
     * 判断是否需要重试
     */
    private boolean shouldRetry(Exception e, Retry retry) {
        // 检查不重试的异常类型
        for (Class<? extends Throwable> noRetryException : retry.noRetryFor()) {
            if (noRetryException.isAssignableFrom(e.getClass())) {
                return false;
            }
        }
        
        // 检查重试的异常类型
        for (Class<? extends Throwable> retryException : retry.retryFor()) {
            if (retryException.isAssignableFrom(e.getClass())) {
                return true;
            }
        }
        
        return false;
    }

    /**
     * 计算重试延迟
     */
    private long calculateDelay(Retry retry, int attempt, long currentDelay) {
        long nextDelay;
        
        switch (retry.policy()) {
            case FIXED_DELAY:
                nextDelay = retry.delay();
                break;
                
            case LINEAR_BACKOFF:
                nextDelay = retry.delay() * attempt;
                break;
                
            case EXPONENTIAL_BACKOFF:
            default:
                nextDelay = (long) (currentDelay * retry.multiplier());
                break;
        }
        
        // 限制最大延迟
        nextDelay = Math.min(nextDelay, retry.maxDelay());
        
        // 添加随机化
        if (retry.randomize()) {
            nextDelay = (long) (nextDelay * (0.5 + random.nextDouble() * 0.5));
        }
        
        return nextDelay;
    }

    /**
     * 获取方法名
     */
    private String getMethodName(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getDeclaringType().getSimpleName() + "." + signature.getName();
    }
}
